﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
using BrainTechLLC.ThreadSafeObjects;
using LinFu.AOP.Interfaces;

namespace BrainTechLLC
{
	public enum MockMode
	{
		None = 0,
		Recording = 1,
		Playback = 2
	}

	public static class AOP
	{
		public static void RegisterWithAOP(this Type t)
		{
			t.Begin<LogableAttribute, LogWrapper>();
			t.Begin<InvokeIfThenElseAttribute, IfThenElseWrapper>();
			t.Begin<AssertBeforeAfterAttribute, AssertBeforeAfterWrapper>();            
			t.Begin<TimeMethodAttribute, TimeMethodWrapper>();
			t.Begin<CountCallsAttribute, CountCallsWrapper>();
			t.Begin<LogPropertyChangedAttribute, LogPropChangedWrapper>();
			t.Begin<PropertyChangedActionAttribute, PropChangedActionWrapper>();
			t.Begin<RecordParametersAttribute, RecordParametersWrapper>();
			t.Begin<RecordPropertyChangeAttribute, PropChangedTrackerWrapper>();
			//t.Begin<SingleThreadExecutionAttribute, SingleThreadExecutionWrapper>();
			t.Begin<ObservableMethodAttribute, ObservableMethodWrapper>();
			t.Begin<ObservablePropertyAttribute, ObservablePropertyWrapper>();
			t.Begin<SecurityRestrictionAttribute, SecurityRestrictionWrapper>();
			t.Begin<LazyCreateAttribute, LazyCreateWrapper>();
			t.Begin<RestrictPropertyChangeAttribute, RestrictPropertyChangeWrapper>(0);

			t.BeginMethodReplacement<RetryAttribute, RetryMethodReplacement>();
			t.BeginMethodReplacement<SingleThreadExecutionAttribute, SingleThreadExecutionReplacement>();
		}

		public static string DotMethodName(this Type t, string methodName)
		{
			return t.Name + "." + methodName;
		}
		
		internal static void RegisterAOPTypes(this Assembly a)
		{
			if (a == null) return;
			List<Type> aopTypes = a.TypesThatImplement<IModifiableType>();
            foreach (var aopType in aopTypes)
            {
                aopType.RegisterWithAOP();
            }
		}

		public static bool GetClassIsMockable(this Type t)
		{
			ClassMethodsMockableAttribute[] attrs = t.ReadAttribute<ClassMethodsMockableAttribute>();
			bool mockable = false;

			if (attrs != null && attrs.Length > 0)
			{
				foreach (ClassMethodsMockableAttribute attr in attrs)
				{
					if (attr.Mockable)
					{
						mockable = true;
						break;
					}
				}
			}

			return mockable;
		}

		public static bool GetClassIsMockable<T>() where T : class
		{
			return GetClassIsMockable(typeof(T));
		}

		public static void EnableAOP()
		{
			List<Assembly> assemblies = AssemblyAndTypes.GetAllDependentAssemblies();
			assemblies.ForEach(assembly => assembly.RegisterAOPTypes());
		}

		public static void EnableAOPForTesting(string fileName)
		{
			DialogResult result = MessageBox.Show("Record=Yes, Playback=No, None=Cancel", "Mock via AOP", MessageBoxButtons.YesNoCancel);
			if (result == DialogResult.Yes)
			{
				MockObjectRepository.AddMockObjectsFromFile(fileName);
				EnableAOP(MockMode.Recording, fileName);
			}
			else if (result == DialogResult.No)
			{
				EnableAOP(MockMode.Playback, fileName);
			}
			else
			{
				// Do nothing
			}
		}

		public static void EnableAOP(MockMode mockMode)
		{
			EnableAOP();
			switch (mockMode)
			{
				case MockMode.None:
					MockObjectRepository.MockRecordingEnabled = false;
					MockObjectRepository.MockReplayEnabled = false;
					break;

				case MockMode.Playback:
					MockObjectRepository.MockRecordingEnabled = false;
					MockObjectRepository.MockReplayEnabled = true;
					break;

				case MockMode.Recording:
					MockObjectRepository.MockRecordingEnabled = true;
					MockObjectRepository.MockReplayEnabled = false;
					break;
			}
		}

		public static void EnableAOP(MockMode mockMode, string fileName)
		{
			EnableAOP();
			switch (mockMode)
			{
				case MockMode.None:
					MockObjectRepository.MockRecordingEnabled = false;
					MockObjectRepository.MockReplayEnabled = false;
					break;

				case MockMode.Playback:
					MockObjectRepository.EnablePlayback(fileName);
					break;

				case MockMode.Recording:
					MockObjectRepository.EnableRecording(fileName);
					break;
			}
		}

		internal static void RegisterAOPClasses<T>() where T : class
		{
			Assembly.GetAssembly(typeof(T)).RegisterAOPTypes();
		}

		internal static void RegisterAOPClasses<T1, T2>()
			where T1 : class
			where T2 : class
		{
			Assembly.GetAssembly(typeof(T1)).RegisterAOPTypes();
			Assembly.GetAssembly(typeof(T2)).RegisterAOPTypes();
		}

		internal static void RegisterAOPClasses<T1, T2, T3>()
			where T1 : class
			where T2 : class
			where T3 : class
		{
			RegisterAOPClasses<T1, T2>();
			Assembly.GetAssembly(typeof(T3)).RegisterAOPTypes();
		}

		public static void DisableInterception(this object target)
		{
			IModifiableType modified = target as IModifiableType;

			if (modified == null)
				return;

			modified.IsInterceptionDisabled = true;
		}

		internal static void Begin<T1, T2>(this Type t)
			where T1 : class, IReadableAttribute
			where T2 : class, IAroundInvoke, new()
		{
			Begin<T1, T2>(t, int.MaxValue);
		}

		internal static void Begin<T1, T2>(this Type t, int insertAt)
			where T1 : class, IReadableAttribute
			where T2 : class, IAroundInvoke, new()
		{
			bool mockable = false;
			if (typeof(T2) == typeof(RecordParametersWrapper))
				mockable = t.GetClassIsMockable();

			List<MethodInfo> methods = t.ReadMethodInfoWithCustomAttribute<T1>();
			List<PropertyInfo> props = t.ReadPropertyInfoWithCustomAttribute<T1>();

			if (mockable)
			{
				var provider = new SimpleAroundInvokeProvider(new T2(), c => !c.TargetMethod.IsStatic && c.TargetType == t);
				int count = AroundInvokeRegistry.Providers.Count;

				if (insertAt > count - 1)
					AroundInvokeRegistry.Providers.Add(provider);
				else
					AroundInvokeRegistry.Providers.Insert(insertAt, provider);
			}

			for (int n = 0; n < methods.Count; n++)
			{
				MethodInfo method = methods[n];
				T1[] attr = method.ReadAttribute<T1>();
				if (attr != null && attr.Length > 0)
				{
					var provider = new SimpleAroundInvokeProvider(new T2(), c => c.TargetMethod.Name == method.Name && c.TargetType == t);
					int count = AroundInvokeRegistry.Providers.Count;

					if (insertAt > count - 1)
						AroundInvokeRegistry.Providers.Add(provider);
					else
						AroundInvokeRegistry.Providers.Insert(insertAt, provider);
				}
			}

			for (int n = 0; n < props.Count; n++)
			{
				PropertyInfo prop = props[n];
				T1[] attr = prop.ReadAttribute<T1>();
				if (attr != null && attr.Length > 0)
				{
					var provider = new SimpleAroundInvokeProvider(new T2(), c => (c.TargetMethod.Name == prop.GetSetMethod().Name || c.TargetMethod.Name == prop.GetGetMethod().Name) && c.TargetType == t);
					int count = AroundInvokeRegistry.Providers.Count;

					if (insertAt > count - 1)
						AroundInvokeRegistry.Providers.Add(provider);
					else
						AroundInvokeRegistry.Providers.Insert(insertAt, provider);
				}
			}
		}

		internal static void BeginMethodReplacement<T1, T2>(this Type t)
			where T1 : class, IReadableAttribute
			where T2 : class, IMethodReplacementProvider, new()
		{
			BeginMethodReplacement<T1, T2>(t, int.MaxValue);
		}

		public static void BeginMethodReplacement<T1, T2>(this Type t, int insertAt)
			where T1 : class, IReadableAttribute
			where T2 : class, IMethodReplacementProvider, new()
		{
			List<MethodInfo> methods = t.ReadMethodInfoWithCustomAttribute<T1>();

			for (int n = 0; n < methods.Count; n++)
			{
				MethodInfo method = methods[n];
				T1[] attr = method.ReadAttribute<T1>();
				if (attr != null && attr.Length > 0)
				{
					var provider = new SimpleMethodReplacement(new T2(), c => c.TargetMethod.Name == method.Name && c.TargetType == t);
					int count = MethodReplacementRegistry.Providers.Count;

					if (insertAt > count - 1)
						MethodReplacementRegistry.Providers.Add(provider);
					else
						MethodReplacementRegistry.Providers.Insert(insertAt, provider);
				}
			}
		}

		public static bool HasObservableMethod(this Type t, string methodName)
		{
			//MethodInfo method = Lookups.GeneralLookup[t.DotMethodName(methodName)];
			MethodInfo method = Lookups.GeneralLookup.Get(t.DotMethodName(methodName), v => t.GetMethod(methodName));

			//if (method == null)
			//{
			//    method = t.GetMethod(methodName);
			//    Lookups.GeneralLookup.AddOrSet(t.DotMethodName(methodName), method);
			//}

			if (method != null)
			{
				ObservableMethodAttribute[] attrs = method.ReadAttribute<ObservableMethodAttribute>();
				if (attrs != null && attrs.Length > 0)
				{
					for (int n = 0; n < attrs.Length; n++)
					{
						if (attrs[n].IsObservable)
							return true;
					}
				}
			}

			return false;
		}

		public static List<MethodInfo> ListObservableMethods(this Type t)
		{
			return t.ReadMethodInfoWithCustomAttribute<ObservableMethodAttribute>();
		}

		public static Observable FindObservableMethod(this Type t, string methodName)
		{
			return Lookups.ObservableMethods.Get(t.DotMethodName(methodName));
		}

		public static Observable FindObservableMethod<T>(string methodName) where T : class
		{
			return Lookups.ObservableMethods.Get(typeof(T).DotMethodName(methodName));
		}

		public static List<PropertyInfo> ListObservableProperties(this Type t)
		{
			return t.ReadPropertyInfoWithCustomAttribute<ObservablePropertyAttribute>();
		}

		public static Observable FindObservableProperty(this Type t, string propertyName)
		{
			return Lookups.ObservableProperties.Get(t.Name + "." + propertyName);
		}

		public static Observable FindObservableProperty<T>(string propertyName) where T : class
		{
			return Lookups.ObservableProperties.Get(typeof(T).Name + "." + propertyName);
		}

		public static bool HasObservableMethod<T>(string methodName) where T : class
		{
			Type t = typeof(T);
			return t.HasObservableMethod(methodName);
		}
	}

	public static class Misc
	{
		public static void PerformOnUIThread(this Control control, Action del)
		{
			if (control.InvokeRequired)
			{
				try
				{
					control.Invoke(new Action(del));
				}
				catch (Exception ex)
				{
					Debug.WriteLine(ex.ToString());
				}
			}
			else
			{
				del();
			}
		}
	}
}

